from motor.motor_asyncio import AsyncIOMotorCollection
from bson import ObjectId
from typing import List, Optional, Dict, Any, Mapping
from backend.services.exceptions import InstanceAlreadyExistsError, InvalidObjectIdError, InstanceNotFoundError
from backend.models.instance import (
    CreateDwfInstanceModel,
    UpdateDwfInstanceModel,
)


async def get_by_id(collection: AsyncIOMotorCollection, instance_id: str) -> Mapping[str, Any]:
    if not ObjectId.is_valid(instance_id):
        raise InvalidObjectIdError(f"Invalid ObjectId: {instance_id}")

    instance = await collection.find_one({"_id": ObjectId(instance_id)})
    if not instance:
        raise InstanceNotFoundError
    return instance


async def list_instances(
    collection: AsyncIOMotorCollection,
    name: str | None,
    page: int,
    page_size: int
) -> Dict[str, Any]:
    query_filter = {}

    if name:
        query_filter["name"] = {"$regex": name, "$options": "i"}

    total_count = await collection.count_documents(query_filter)
    skip_count = page * page_size
    cursor = collection.find(query_filter).skip(skip_count).limit(page_size)
    items = await cursor.to_list(length=page_size)

    return {"total_count": total_count, "items": items}


async def create(collection: AsyncIOMotorCollection, instance: CreateDwfInstanceModel) -> dict:
    if await collection.find_one({"name": instance.name}):
        raise InstanceAlreadyExistsError(f"Instance with name '{instance.name}' already exists.")

    instance_dict = instance.model_dump()
    result = await collection.insert_one(instance_dict)
    return  await collection.find_one({"_id": result.inserted_id})


async def update(
        collection: AsyncIOMotorCollection, instance_id: str, instance_in: UpdateDwfInstanceModel
) -> Mapping[str, Any]:
    if not ObjectId.is_valid(instance_id):
        raise InvalidObjectIdError(f"Invalid ObjectId: {instance_id}")

    update_data = instance_in.model_dump(exclude_unset=True)
    if not update_data:
        raise ValueError("No update data provided")

    if "name" in update_data:
        if await collection.find_one({"name": update_data["name"], "_id": {"$ne": ObjectId(instance_id)}}):
            raise InstanceAlreadyExistsError(f"Instance with name '{update_data['name']}' already exists.")

    result = await collection.find_one_and_update(
        {"_id": ObjectId(instance_id)},
        {"$set": update_data},
        return_document=True
    )
    if not result:
        raise InstanceNotFoundError(f"Instance with id {instance_id} not found")
    return result


async def delete(collection: AsyncIOMotorCollection, instance_id: str) -> bool:
    delete_result = await collection.delete_one({"_id": ObjectId(instance_id)})
    return delete_result.deleted_count > 0

